/*
* serial.c - OpenWii DVD Drive CN302 serial port access functions
* Copyright 2006-2008 OpenWii (admin@openwii.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* Version 2.4 by Andii
*/

#include "common.h"

uint16_t ret_word;

// wait approx 1 us
static void waitus(uint16_t us)
{
	int16_t t0 = TCNT1;
	us *= (uint8_t)USTIMERDIV;	// us now = how long 1 ms takes to pass (this requires mathlibs and more space...)
	while ((TCNT1 - t0) < us);	// only works correctly if timer is not in compare mode -krall
}

// transmit 1 byte via serial interface
static void TxByte(uint8_t byte)
{
	uint8_t y;
	for (y = 0; y < 8; y++)
	{
		CLKLOW;		// drive clock low
		waitus(SERCLKDLY);
		if (byte & 0x1)
		{
			OUTHIZ;	// make output pin highZ
		}
		else
		{
			OUTLOW;	// drive output pin low
		}
		byte >>= 1;
		CLKHIZ;		// HighZ clock pin
		waitus(SERCLKDLY);
	}
	waitus(100);
	CLKLOW;		// drive clock low
	OUTLOW;		// drive output low
}

// receive 1 byte via serial
static uint8_t RxByte()
{
	uint8_t byte = 0;
	uint8_t y;
	for (y = 0; y < 8; y++)
	{
		CLKLOW;
		waitus(SERCLKDLY);
		byte |= GETIN << y;
		CLKHIZ;
		waitus(SERCLKDLY);
	}
	waitus(100);
	CLKLOW;
	return byte;
}

// establish Sync
void Sync()
{
	uint16_t syncword = 0;
	do
	{
		CLKLOW;
		waitus(100);
		syncword <<= 1;
		syncword |= GETIN;
		CLKHIZ;
		waitus(100);
	}
	while (syncword != 0x7777);
	CLKLOW;
}

// activate a serial command
uint8_t SerialRequest(uint8_t command, uint32_t offset, uint16_t data)
{
	uint8_t buffer[SERIAL_CMD_SIZE];	// command Buffer
	uint16_t ret;
	uint8_t x;
	uint8_t size = command >> 4;

	command |= 0xf0;
	buffer[0] = command;
	buffer[1] = 0;
	buffer[2] = (uint8_t)(offset >> 8);
	buffer[3] = (uint8_t)offset;
	buffer[4] = (uint8_t)data;
	buffer[5] = (uint8_t)(offset >> 16);
	buffer[6] = (uint8_t)(data >> 8);
	buffer[7] = 0;
	buffer[8] = size;
	buffer[9] = 0;

	for (x = 0; x < SERIAL_CMD_SIZE; x++)
	{
		TxByte(buffer[x]);
	}
	waitus(1000);

	// zero the last result
	ret_word = 0;

	// get and check manditory part of the reply
	if (RxByte() | RxByte())
	{
		return 0;
	}
	ret = RxByte();
	if (size == 2)
	{
		ret |= ((uint16_t)RxByte() << 8);
	}

	if (command == CMD_WRITE)
	{
		if (ret != data)
		{
			return 0;
		}
	}

	ret_word = ret;
	return 1;
}

// Send out a block of data as efficiently as possible
void SendBlock(uint32_t offset, const uint8_t* buff, uint16_t size)
{
	while (size >= 2)
	{	// if we find way to increase size, say to 4, change all these 2 to 4
		SerialRequest(CMD_WRITE16, offset, pgmReadWord((PGM_P)buff));
		offset += 2;
		buff += 2;
		size -= 2;
	}
	if (size)
	{
		SerialRequest(CMD_WRITE8, offset, pgmReadWord((PGM_P)buff));
	}
}
